﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "GLObjectManager.h"
#include "GLStateManager.h"
#include "../../OS/Utils/IThreadOp.h"
#include "../../OS/Utils/CThreadManager.h"

namespace UX_STL
{
namespace GLShell
{

#ifdef UX_GL_USE_VAO

	typedef array< GLuint, TFastCopyStrategy< GLuint > >				vao_array_t;

	
	//
	// Create VAOs Operation
	//

	class CCreateVAOsOp : public IThreadOpC
	{
	protected:
		Mutex	_sCSLock;
		atomic_int32		_sCounter;
		vao_array_t			_aVAOs;
		uint				_uNumVAOs;

	public:
		CCreateVAOsOp(uint n): _uNumVAOs(n)
		{}

		virtual ~CCreateVAOsOp()
		{}

		virtual void SetCounter(int i)
		{
			_sCounter = i;
		}

		virtual void Free()
		{
			delete this;
		}

		virtual void Do()
		{
			bool		res;
			vao_array_t	arr;

			arr.Resize( _uNumVAOs, false );

			GL_CALL_B( glGenVertexArrays( _uNumVAOs, arr.ptr() ), res );

			DEBUG_ONLY(
				_sCSLock.Lock();

				for (uint i = 0; i < _uNumVAOs; ++i)
					res &= _aVAOs[i] == arr[i];

				_sCSLock.Unlock();

				ASSERT( res );
			);

			if ( --_sCounter <= 0 )
				delete this;
		}

		bool Create()
		{
			_aVAOs.Resize( _uNumVAOs, false );
			GL_CALL_R( glGenVertexArrays( _uNumVAOs, _aVAOs.ptr() ) );
			return true;
		}

		const vao_array_t & GetVAOs() const
		{
			return _aVAOs;
		}
	};


	//
	// Delete VAOs Operation with sync
	//

	class CDeleteVAOsOpSync : public IThreadOpC
	{
	protected:
		Mutex		_sCSLock;
		SyncEvent				_sESync;
		int						_iCounter;
		const vao_array_t &		_aVAOs;

	public:
		CDeleteVAOsOpSync(const vao_array_t &vao): _aVAOs(vao), _iCounter(0), _sESync( SyncEvent::AUTO_RESET )
		{}

		virtual ~CDeleteVAOsOpSync()
		{}
		
		virtual void SetCounter(int i)
		{
			_iCounter = i;
		}

		virtual void Free()
		{
			delete this;
		}

		virtual void Do()
		{
			bool	b_unlock = false;

			_sCSLock.Lock();

			GL_CALL( glDeleteVertexArrays( (GLsizei) _aVAOs.Count(), _aVAOs.ptr() ) );

			if ( --_iCounter <= 0 )
				b_unlock = true;

			_sCSLock.Unlock();

			if ( b_unlock )
				_sESync.Signal();
		}

		bool Sync()
		{
			return _sESync.Wait();
		}
	};



	//
	// Set VAO Attributes Operation
	//

	class CSetAttribsOp : public IThreadOpC
	{
	protected:
		atomic_int32			_sCounter;
		va_array_t				_aAttribs;
		const GLuint			_uVAO;

	public:
		CSetAttribsOp(GLuint uVAO, const uni_array<const GLVertexAttribExt> &aAttribs): _uVAO(uVAO)
		{
			FOR( i, _aAttribs ) {
				_aAttribs[i] = i < aAttribs.Count() ? aAttribs[i] : GLVertexAttribExt();
			}
		}

		virtual ~CSetAttribsOp()
		{}

		virtual void SetCounter(int i)
		{
			_sCounter = i;
		}

		virtual void Free()
		{
			delete this;
		}

		virtual void Do()
		{
			CHECK( GLObjectManager::s_SetAttribs( _uVAO, _aAttribs ) );

			if ( --_sCounter <= 0 )
				delete this;
		}
	};



	const uint	GLObjectManager::_s_uMinVAOSize = 20;


/*
=================================================
	конструктор
=================================================
*/
	GLObjectManager::GLObjectManager():
		_pReallocVAOs(null),	_pDeleteVAOs(null),	_pSetAttribs(null)
	{}
	
/*
=================================================
	деструктор
=================================================
*/
	GLObjectManager::~GLObjectManager()
	{}
	
/*
=================================================
	инициализация VAO общих для всех контекстов
=================================================
*/
	bool GLObjectManager::Init(uint uNumVAO, bool bMT)
	{
		if ( uNumVAO == 0 )
			uNumVAO = 10000;


		if ( bMT )
		{
			_pReallocVAOs	= & GLObjectManager::_MTReallocVAOs;
			_pDeleteVAOs	= & GLObjectManager::_MTDeleteVAOs;
			_pSetAttribs	= & GLObjectManager::_MTSetAttribs;
		}
		else
		{
			_pReallocVAOs	= & GLObjectManager::_STReallocVAOs;
			_pDeleteVAOs	= & GLObjectManager::_STDeleteVAOs;
			_pSetAttribs	= & GLObjectManager::_STSetAttribs;
		}

		return _ReallocVAOs( uNumVAO );
	}

/*
=================================================
	удалить все VAO
	(нужно вызвать перед удалением контекстов)
=================================================
*/
	void GLObjectManager::Destroy()
	{
		SCOPELOCK( _sCSLockVAO );
		ASSERT( _aFreeVAOs.Count() == _aAllVAOs.Count() );

		CHECK( (this->*_pDeleteVAOs)() );
		_CheckNotReleasedObjects();
	}
	
/*
=================================================
	добавить VAO
=================================================
*/
	inline bool GLObjectManager::_ReallocVAOs(uint uCount)
	{
		return (this->*_pReallocVAOs)( uCount );
	}
	
/*
=================================================
	получить индексы свободных VAO
=================================================
*/
	bool GLObjectManager::GenVertexArrays(const GLsizei n, GLuint * arrays)
	{
		if ( arrays == null or n <= 0 )
			return false;

		SCOPELOCK( _sCSLockVAO );

		if ( n >= (GLsizei)_aFreeVAOs.Count() )
			CHECK_ERR( _ReallocVAOs( n ) );

		GLsizei		i = n;

		for (--i; i >= 0; --i)
		{
			arrays[i] = _aFreeVAOs.Back();
			_aFreeVAOs.PopBack();
		}

		SMAN()->GetStatistic()->uVAOs += n;
		return true;
	}

/*
=================================================
	освободить индексы VAO
=================================================
*/
	bool GLObjectManager::DeleteVertexArrays(const GLsizei n, const GLuint * arrays)
	{
		if ( arrays == null or n <= 0 )
			return false;
		
		SCOPELOCK( _sCSLockVAO );

		_aFreeVAOs.Append( uni_array< const GLuint>( arrays, n ) );
		SMAN()->GetStatistic()->uVAOs -= n;
		return true;
	}
	
/*
=================================================
	устанавливает атрибуты для VAO в текущем контексте
=================================================
*/
	bool GLObjectManager::s_SetAttribs(GLuint uVAO, const uni_array<const GLVertexAttribExt> &aAttribs)
	{
		bool	b_res = true;

		// dsable all attribs
		for (int i = 0; i < C_GL_MAX_VERTEX_ATTRIBS; ++i) {
			GL_CALL( glDisableVertexArrayAttrib( uVAO, i ) );
		}

		FOR( i, aAttribs )
		{
			GLVertexAttribExt const &	attrib = aAttribs[i];
			
			
			if ( attrib.uLocation == MaxValue(attrib.uLocation) )
				continue;

			ASSERT( attrib.uLocation < C_GL_MAX_VERTEX_ATTRIBS );

			
			GL_CALL_BA( glEnableVertexArrayAttrib( uVAO, attrib.uLocation ), b_res );
			//GL_CALL_BA( glVertexAttribDivisor( uVAO, attrib.uLocation, attrib.uDivisor ), b_res );

			if ( !attrib.bInteger and !attrib.bLong )
			{
				GL_CALL_BA( glVertexArrayVertexAttribOffset(	uVAO,
																attrib.uBuffer,
																attrib.uLocation,
																attrib.uCount,
																attrib.eType,
																attrib.bNormalize,
																attrib.uStride,
																attrib.uOffset ), b_res );
			}
			if ( attrib.bInteger )
			{
				GL_CALL_BA( glVertexArrayVertexAttribIOffset(	uVAO,
																attrib.uBuffer,
																attrib.uLocation,
																attrib.uCount,
																attrib.eType,
																attrib.uStride,
																attrib.uOffset ), b_res );
			}
			else
			if ( attrib.bLong )
			{
				GL_CALL_BA( glVertexArrayVertexAttribLOffset(	uVAO,
																attrib.uBuffer,
																attrib.uLocation,
																attrib.uCount,
																attrib.eType,
																attrib.uStride,
																attrib.uOffset ), b_res );
			}
		}

		return b_res;
	}
	
/*
=================================================
	возвращает атрибуты VAO в текущем контексте
=================================================
*/
	bool GLObjectManager::s_GetAttribs(GLuint uVAO, GLVerticesAttribsExt_t &aAttribs)
	{
		SMAN()->PushVertexArray();
		SMAN()->BindVertexArray( uVAO );

		for (int i = 0; i < C_GL_MAX_VERTEX_ATTRIBS; ++i)
		{
			bool	b_ret	= true;
			GLint	i_temp	= 0;

			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &i_temp ), b_ret );

			if ( i_temp != GL_TRUE )
				continue;

			GLVertexAttribExt	s_attrib;
			void		*		ptr;
			
			s_attrib.uLocation	= i;

			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &i_temp ), b_ret );
			s_attrib.uBuffer	= (uint)i_temp;

			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &i_temp ), b_ret );
			s_attrib.uStride	= (uint)i_temp;
			
			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &i_temp ), b_ret );
			s_attrib.uCount		= (uint)i_temp;
			
			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &i_temp ), b_ret );
			s_attrib.eType		= (gl_vertex::type)i_temp;
			
			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, &i_temp ), b_ret );
			s_attrib.uDivisor	= (uint)i_temp;
			
			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_LONG, &i_temp ), b_ret );
			s_attrib.bLong		= !!i_temp;
			
			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_INTEGER, &i_temp ), b_ret );
			s_attrib.bInteger	= !!i_temp;

			GL_CALL_BA( glGetVertexAttribiv( i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &i_temp ), b_ret );
			s_attrib.bNormalize	= !!i_temp;
			
			GL_CALL_BA( glGetVertexAttribPointerv( i, GL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr ), b_ret );
			s_attrib.uOffset	= (uint)ptr;

			if ( !b_ret ) {
				LOG( (string() << "GL errors while get attrib params, location: " << i).cstr(), e_log_msg::ERROR_4 );
				b_ret = true;
			}

			aAttribs.PushBack( s_attrib );
		}

		SMAN()->PopVertexArray();
		return true;
	}

/*
=================================================
	добавить VAO
=================================================
*/
	bool GLObjectManager::_MTReallocVAOs(uint uCount)
	{
		const uint		u_count	= UXMath::Max( uCount, _s_uMinVAOSize );
		CCreateVAOsOp *	p_op	= new CCreateVAOsOp( u_count );
		
		{
			SCOPELOCK( _sCSLockVAO );

			if ( !p_op->Create() )
			{
				delete p_op;
				return false;
			}

			_aAllVAOs.Append( p_op->GetVAOs() );
			_aFreeVAOs.Append( p_op->GetVAOs() );
		}

		CHECK_ERR( THREADMAN()->PushOpToAll( e_thread::_RENDER_CONTEXT, p_op, false, true ) );
		return true;
	}
	
/*
=================================================
	удалить VAO
=================================================
*/
	bool GLObjectManager::_MTDeleteVAOs()
	{
		CDeleteVAOsOpSync *	p_op = new CDeleteVAOsOpSync( _aAllVAOs );

		CHECK_ERR( THREADMAN()->PushOpToAll( e_thread::_RENDER_CONTEXT, p_op, false, true ) );

		bool res = p_op->Sync();
		p_op->Free();

		return res;
	}
	
/*
=================================================
	установить утрибуты VAO
=================================================
*/
	bool GLObjectManager::_MTSetAttribs(GLuint uVAO, const uni_array<const GLVertexAttribExt> &aAttribs)
	{
		CSetAttribsOp *	p_op = new CSetAttribsOp( uVAO, aAttribs );

		CHECK_ERR( THREADMAN()->PushOpToAll( e_thread::_RENDER_CONTEXT, p_op, false, true ) );
		return s_SetAttribs( uVAO, aAttribs );
	}
		
/*
=================================================
	добавить VAO
=================================================
*/
	bool GLObjectManager::_STReallocVAOs(uint uCount)
	{
		const uint	u_count	= Max( uCount, _s_uMinVAOSize );

		vao_array_t	a_vaos;
		a_vaos.Resize( u_count, false );

		GL_CALL_R( glGenVertexArrays( u_count, a_vaos.ptr() ) );
		
		SCOPELOCK( _sCSLockVAO );
		
		_aAllVAOs.Append( a_vaos );
		_aFreeVAOs.Append( a_vaos );

		return true;
	}
	
/*
=================================================
	удалить VAO
=================================================
*/
	bool GLObjectManager::_STDeleteVAOs()
	{
		GL_CALL_R( glDeleteVertexArrays( (GLsizei) _aAllVAOs.Count(), _aAllVAOs.ptr() ) );
		return true;
	}
	
/*
=================================================
	установить утрибуты VAO
=================================================
*/
	bool GLObjectManager::_STSetAttribs(GLuint uVAO, const uni_array<const GLVertexAttribExt> &aAttribs)
	{
		return s_SetAttribs( uVAO, aAttribs );
	}

#else	

/*
=================================================
	конструктор
=================================================
*/
	GLObjectManager::GLObjectManager()
	{
	}
	
/*
=================================================
	деструктор
=================================================
*/
	GLObjectManager::~GLObjectManager()
	{
	}
	
/*
=================================================
	инициализация VAO общих для всех контекстов
=================================================
*/
	bool GLObjectManager::Init(uint uNumVAO, bool bMT)
	{
		return true;
	}

/*
=================================================

=================================================
*/
	void GLObjectManager::Destroy()
	{
		_CheckNotReleasedObjects();
	}

#endif	// UX_GL_USE_VAO

	
/*
=================================================
	проверяет остались ли не удаленные объекты
=================================================
*/
	void GLObjectManager::_CheckNotReleasedObjects()
	{
#	define CHECK_GL_OBJ( _val, _name )		if ( _val > 0 ) { b_leak = true;  s_info << _name << ": " << _val << "\n"; }

		string			s_info( "Check memory leaks:\n" );
		bool			b_leak = false;
		GLStatistic *	p_stat = SMAN()->GetStatistic();

		CHECK_GL_OBJ( p_stat->uBuffers,				"buffers" );
		CHECK_GL_OBJ( p_stat->uFrameBuffers,		"framebuffers" );
		CHECK_GL_OBJ( p_stat->uProgramPipelines,	"program pipelines" );
		CHECK_GL_OBJ( p_stat->uPrograms,			"programs" );
		CHECK_GL_OBJ( p_stat->uQueries,				"queries" );
		CHECK_GL_OBJ( p_stat->uRenderBuffers,		"renderbuffers" );
		CHECK_GL_OBJ( p_stat->uSamplers,			"samplers" );
		CHECK_GL_OBJ( p_stat->uShaders,				"shaders" );
		CHECK_GL_OBJ( p_stat->uSyncs,				"sync objects" );
		CHECK_GL_OBJ( p_stat->uTextures,			"textures" );
		CHECK_GL_OBJ( p_stat->uTransformFeedbacks,	"transform feedbacks" );
		CHECK_GL_OBJ( p_stat->uVAOs,				"vertex arrays" );

		if ( b_leak )
			LOG( s_info.cstr(), e_log_msg::WARNING_NL_0 );

#	undef CHECK_GL_OBJ
	}

/*
=================================================
	обертки на GL-функциями
=================================================
*/
	bool GLObjectManager::GenBuffers(GLsizei n, GLuint * buffers) const
	{
		GL_CALL_R( glGenBuffers( n, buffers ) );
		SMAN()->GetStatistic()->uBuffers += n;
		return true;
	}


	bool GLObjectManager::GenFramebuffers(GLsizei n, GLuint * ids) const
	{
		GL_CALL_R( glGenFramebuffers( n, ids ) );
		SMAN()->GetStatistic()->uFrameBuffers += n;
		return true;
	}


	bool GLObjectManager::GenProgramPipelines(GLsizei n, GLuint * pipelines) const
	{
		GL_CALL_R( glGenProgramPipelines( n, pipelines ) );
		SMAN()->GetStatistic()->uProgramPipelines += n;
		return true;
	}


	bool GLObjectManager::GenQueries(GLsizei n, GLuint * ids) const
	{
		GL_CALL_R( glGenQueries( n, ids ) );
		SMAN()->GetStatistic()->uQueries += n;
		return true;
	}


	bool GLObjectManager::GenRenderbuffers(GLsizei n, GLuint * renderbuffers) const
	{
		GL_CALL_R( glGenRenderbuffers( n, renderbuffers ) );
		SMAN()->GetStatistic()->uRenderBuffers += n;
		return true;
	}


	bool GLObjectManager::GenSamplers(GLsizei n, GLuint * samplers) const
	{
		GL_CALL_R( glGenSamplers( n, samplers ) );
		SMAN()->GetStatistic()->uSamplers += n;
		return true;
	}


	bool GLObjectManager::GenTransformFeedbacks(GLsizei n, GLuint * ids) const
	{
		GL_CALL_R( glGenTransformFeedbacks( n, ids ) );
		SMAN()->GetStatistic()->uTransformFeedbacks += n;
		return true;
	}


	bool GLObjectManager::GenTextures(GLsizei n, GLuint * textures) const
	{
		GL_CALL_R( glGenTextures( n, textures ) );
		SMAN()->GetStatistic()->uTextures += n;
		return true;
	}

	/*
	bool GLObjectManager::GenVertexArrays(GLsizei n, GLuint * arrays)
	{
		GL_CALL_R( glGenVertexArrays( n, arrays ) );
		return true;
	}
	*/

	bool GLObjectManager::CreateShader(GLenum shaderType, GLuint &shader) const
	{
		GL_CALL_R( shader = glCreateShader( shaderType ) );
		SMAN()->GetStatistic()->uShaders++;
		return true;
	}


	bool GLObjectManager::CreateProgram(GLuint &program) const
	{
		GL_CALL_R( program = glCreateProgram() );
		SMAN()->GetStatistic()->uPrograms++;
		return true;
	}
	

	bool GLObjectManager::FenceSync(GLenum condition, GLbitfield flags, GLsync &sync) const
	{
		GL_CALL_R( sync = glFenceSync( condition, flags ) );
		SMAN()->GetStatistic()->uSyncs++;
		return true;
	}



	bool GLObjectManager::DeleteBuffers(GLsizei n, const GLuint * buffers) const
	{
		GL_CALL_R( glDeleteBuffers( n, buffers ) );
		SMAN()->GetStatistic()->uBuffers -= n;
		return true;
	}


	bool GLObjectManager::DeleteFramebuffers(GLsizei n, const GLuint * ids) const
	{
		GL_CALL_R( glDeleteFramebuffers( n, ids ) );
		SMAN()->GetStatistic()->uFrameBuffers -= n;
		return true;
	}


	bool GLObjectManager::DeleteProgramPipelines(GLsizei n, const GLuint * pipelines) const
	{
		GL_CALL_R( glDeleteProgramPipelines( n, pipelines ) );
		SMAN()->GetStatistic()->uProgramPipelines -= n;
		return true;
	}


	bool GLObjectManager::DeleteQueries(GLsizei n, const GLuint * ids) const
	{
		GL_CALL_R( glDeleteQueries( n, ids ) );
		SMAN()->GetStatistic()->uQueries -= n;
		return true;
	}


	bool GLObjectManager::DeleteRenderbuffers(GLsizei n, const GLuint * renderbuffers) const
	{
		GL_CALL_R( glDeleteRenderbuffers( n, renderbuffers ) );
		SMAN()->GetStatistic()->uRenderBuffers -= n;
		return true;
	}


	bool GLObjectManager::DeleteSamplers(GLsizei n, const GLuint * samplers) const
	{
		GL_CALL_R( glDeleteSamplers( n, samplers ) );
		SMAN()->GetStatistic()->uSamplers -= n;
		return true;
	}


	bool GLObjectManager::DeleteTransformFeedbacks(GLsizei n, const GLuint * ids) const
	{
		GL_CALL_R( glDeleteTransformFeedbacks( n, ids ) );
		SMAN()->GetStatistic()->uTransformFeedbacks -= n;
		return true;
	}


	bool GLObjectManager::DeleteTextures(GLsizei n, const GLuint * textures) const
	{
		GL_CALL_R( glDeleteTextures( n, textures ) );
		SMAN()->GetStatistic()->uTextures -= n;
		return true;
	}

	/*
	bool GLObjectManager::DeleteVertexArrays(GLsizei n, const GLuint * arrays)
	{
		GL_CALL_R( glDeleteVertexArrays( n, arrays ) );
		return true;
	}
	*/

	bool GLObjectManager::DeleteShader(GLuint shader) const
	{
		GL_CALL_R( glDeleteShader( shader ) );
		SMAN()->GetStatistic()->uShaders--;
		return true;
	}


	bool GLObjectManager::DeleteProgram(GLuint program) const
	{
		GL_CALL_R( glDeleteProgram( program ) );
		SMAN()->GetStatistic()->uPrograms--;
		return true;
	}
	

	bool GLObjectManager::DeleteSync(GLsync sync) const
	{
		GL_CALL_R( glDeleteSync( sync ) );
		SMAN()->GetStatistic()->uSyncs--;
		return true;
	}
	
//-------------------------------------------------------------------
	
}	// GLShell
}	// UX_STL